iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0
Software Development

我的SpringBoot絕學:7+2個專案,從新手變專家系列 第 26

Day26 前端專案:React(2) 待辦事項清單的整合與實作

  • 分享至 

  • xImage
  •  

從SpringBoot取得資料

目前表格顯示的是假資料,我們要從SpringBoot專案取得真實的資料。

我們選擇的SpringBoot專案是待辦事項清單(第二個專案)。

安裝axios,用來和RESTful API溝通。

bun i axios

在Home.jsx添加對應的內容,來取得Todo。

import React, { useEffect, useState } from "react";
import axios from "axios";

export default function Home() {

使用useEffect,在讀取網頁時執行裡面的程式

useEffect(() => {
        loadTodos();
    }, []);

從後端讀取Todo資料,是非同步的操作,所以要用async/await

const loadTodos = async () => {

發送GET請求,讀取所有的Todo,await會等待這個GET請求完成,才繼續往下執行

const res = await axios.get("http://localhost:8080/api/todo/all");

在F12的Console顯示取得的內容

			console.log(res.data);
		};

	//skip
}

處理CORS

現在我們需要修改後端的程式,解決CORS的問題,讓前端能夠使用後端的API。

我們回到Spring Boot專案,修改TodoController.java

@CrossOrigin("http://localhost:5173/")
public class TodoController {

啟動後端專案,同時也啟動前端專案,按下F12選擇Console頁面,重新整理網頁,可以看到有一行

[]

代表我們解決了CORS,前端可以連接後端的API。

新增Todo

目前資料庫中沒有Todo,現在要讓前端能夠新增Todo。

建立src/components/todo/AddTodo.jsx,在網頁上完成新建Todo。

import React, { useState } from "react";
import axios from "axios";

export default function AddTodo() {
	const [todo, setTodo] = useState({
		title: "",
		completed: false
	})

	const {title, completed} = todo;

	const onInputChange = (e) => {
		const { name, value, type, checked } = e.target;
		setTodo({
			...todo,
			[name]: type === "checkbox" ? checked : value
		});
	};

	const onSubmit = async (e) => {
		e.preventDefault();
		await axios.post("http://localhost:8080/api/todo/", todo);
	}

	return (
		<div className="flex justify-center min-h-screen items-center bg-gray-100">
			<form
				method="post"
				role="form"
				className="bg-white p-6 rounded-lg shadow-md w-full max-w-md"
				onSubmit={e => onSubmit(e)}
			>
				<div className="mb-4">
					<label className="text-gray-700 font-bold mb-2">
						Title
					</label>
					<input
						placeholder="Enter todo title"
						type="text"
						className="shadow border rounded w-full py-2 px-3 text-gray-700"
						name="title"
						value={title}
						onChange={e => onInputChange(e)}
					/>
				</div>

				<div className="mb-4">
					<label className="text-gray-700 font-bold mb-2">
						Completed
					</label>
					<br />
					<input type="checkbox" className="mr-2 leading-tight" name="completed" checked={completed} onChange={e => onInputChange(e)} />
				</div>

				<button
					type="submit"
					className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
				>
					Add
				</button>
			</form>
		</div>
	);
}

切換頁面

接下來啟用導航列,讓我們能夠在首頁和新增Todo的頁面之間切換。

安裝react-router-dom,實現路由功能。

bun i react-router-dom

修改App.jsx,添加路由內容。

import React from "react";
import Navbar from "./components/navbar/Navbar";
import Home from "./pages/Home";
import AddTodo from "./components/todo/AddTodo";
import {BrowserRouter as Router, Route, Routes} from "react-router-dom";

function App() {
	return (
		<>
			<Router>
				<Navbar />

				<Routes>
					<Route exact path="/" element={<Home />}></Route>
					<Route exact path="/add" element={<AddTodo />}></Route>
				</Routes>
			</Router>
		</>
	);
}

export default App;

http://localhost:5173/會進入首頁。

http://localhost:5173/add 是新增Todo的網頁。


對Navbar.jsx,做點小修改,讓我們使用它來進入不同的頁面。

//previous content

const navigation = [
	{ name: "Home", href: "/", current: false },
	{ name: "Add Todo", href: "/add", current: false },
];

//previous content

修改AddTodo,當按下送出後,會回到首頁。

let navigate = useNavigate();

const onSubmit = async (e) => {
		e.preventDefault();
		await axios.post("http://localhost:8080/api/todo/", todo);
		navigate("/");
	}

記得import useNavigate,不然會出錯,只會顯示空白畫面。

將後端的Todo導入到網頁上

現在我們將真實的資料顯示在首頁上,修改Home.jsx。

const [todos, setTodos] = useState([]);

將從後端取得的資料傳入todos

const loadTodos = async () => {

		//skip
		setTodos(res.data);
	};

todos.map((todo)和thymeleaf中的th:each類似,將todos的資料一個一個呈現

				<tbody>
					{todos.map((todo) => (
						<tr className="bg-white hover:bg-gray-100">
							<td className="border border-gray-300 px-4 py-2">{todo.id}</td>
							<td className="border border-gray-300 px-4 py-2">{todo.title}</td>
							<td className="border border-gray-300 px-4 py-2">
								{todo.completed ? "Yes" : "No"}
							</td>
							<td className="border border-gray-300 px-4 py-2">
								<div className="space-x-2">
									<a href="#" className="text-blue-500 hover:underline">
										View
									</a>
									<a href="#" className="text-orange-500 hover:underline">
										Edit
									</a>
									<a href="#" className="text-green-500 hover:underline">
										Set Completed
									</a>
									<a href="#" className="text-yellow-500 hover:underline">
										Set Uncompleted
									</a>
									<a href="#" className="text-red-500 hover:underline">
										Delete
									</a>
								</div>
							</td>
						</tr>
					))}
				</tbody>

來到新增頁面,填寫Title、選擇Completed後,按下Add。

就可以在首頁見到剛新增的todo

完成設定Todo狀態和刪除Todo

我們先完成這幾個不需要新增網頁就能運作的功能,修改Home.jsx。

使用useParams取得參數

const {id} = useParams();

將todo狀態改為已完成

const setCompletedTodo = async (id) => {
		await axios.patch(`http://localhost:8080/api/todo/${id}/completed`);
		loadTodos();
	};

將todo狀態改為未完成

const setUnCompletedTodo = async (id) => {
		await axios.patch(`http://localhost:8080/api/todo/${id}/uncompleted`);
		loadTodos();
	};

刪除Todo

const deleteTodo = async (id) => {
		await axios.delete(`http://localhost:8080/api/todo/${id}`);
		loadTodos();
	};

修改表格的td部分,在按下時做出對應的動作。

<a onClick={() => setCompletedTodo(todo.id)} className="text-green-500 hover:underline">
										Set Completed
									</a>
									<a onClick={() => setUnCompletedTodo(todo.id)} className="text-yellow-500 hover:underline">
										Set Uncompleted
									</a>
									<a onClick={() => deleteTodo(todo.id)} className="text-red-500 hover:underline">
										Delete
									</a>

在可以在網頁點擊,做出以下的功能

  • Set Completed設定為完成

原先Completed是No,按下後變為Yes。

  • Set Uncompleted設定為未完成

原先Yes,按下變No。

  • Delete刪除待辦事項

編輯Todo

我們複製貼上AddTodo.jsx的內容到EditTodo.jsx。

將AddTodo改成EditTodo

export default function EditTodo()

我們需要使用useParams取得id,增加這一行

const {id} = useParams();

傳送資料的網址不同了,因此要修改

await axios.put(`http://localhost:8080/api/todo/${id}`, todo);

按鈕的文字從Add改成Edit

<button
					type="submit"
					className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
				>
					Edit
				</button>

我們需要取得特定的todo資料,添加以下程式碼

useEffect(() => {
		loadTodo();
	}, []);
	
	const loadTodo = async () => {
		const res = await axios.get(`http://localhost:8080/api/todo/${id}`);
		setTodo(res.data);
	}

在App.jsx,增加這行,處理修改Todo資料的路徑,以及id會當作參數

<Route exact path="/edit/:id" element={<EditTodo />}></Route>

修改Home.jsx的Edit區塊href的目的地

<a href={`/edit/${todo.id}`} className="text-orange-500 hover:underline">
										Edit
									</a>

現在我們可以透過Edit來修改Todo內容。

按下Edit,前往編輯頁面,修改todo後,按下Edit送出。

回到首頁,發現todo內容變了。

查看指定的Todo

我們使用EditTodo的結構,來完成ViewTodo.jsx。


function名稱改成ViewTodo

export default function ViewTodo()

刪除onInputChange


修改onSubmit

const onSubmit = (e) => {
		e.preventDefault();
		navigate("/");
	};

把form改成div,只留下className的部分

<div className="bg-white p-6 rounded-lg shadow-md w-full max-w-md">

將input改成div

<div className="mr-2 leading-tight">{todo.title}</div>

<div className="mr-2 leading-tight">
						{todo.completed ? "Yes" : "No"}
					</div>

按鈕的文字改成Back,處理按下時的事件

<button
					type="submit"
					className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
					onClick={(e) => onSubmit(e)}
				>
					Back
				</button>

在App.jsx,增加

<Route exact path="/view/:id" element={<ViewTodo />}></Route>

修改Home.jsx

<a href={`/view/${todo.id}`} className="text-blue-500 hover:underline">
										View
									</a>

我們就能透過View來查看單一Todo的內容了,按Back就能回到首頁。


我們的前端React專案完成了,比之前的全端專案更好看。

程式碼

需要自行安裝使用到的npm套件,在專案目錄輸入npm i或bun i即可安裝。

https://mega.nz/file/JctCSbpa#Yq77_mo00a9CNl9AC-cjWpiRy68-rbOg0AvJaIlQYX4


上一篇
Day25 前端專案:React(1) 使用 Vite、Bun 與 Tailwind CSS ,從設定到導航列及首頁設計
下一篇
Day27 前端專案:Vue.js(1)完成註冊、登入與登出
系列文
我的SpringBoot絕學:7+2個專案,從新手變專家31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言